package pl.shenlon.poker.gui;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Scanner;

import javax.swing.AbstractButton;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSpinner;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import net.miginfocom.swing.MigLayout;
import pl.shenlon.assets.Assets;
import pl.shenlon.assets.assetmanagers.AssetManager;
import pl.shenlon.ga.Population;
import pl.shenlon.ga.SimpleSpecimen;
import pl.shenlon.ga.StatefulSpecimen;
import pl.shenlon.ga.evaluation.LinearPopulationEvaluator;
import pl.shenlon.ga.evaluation.PopulationEvaluator;
import pl.shenlon.ga.evaluation.TournamentPopulationEvaluator;
import pl.shenlon.ga.selection.PopulationSpecimenSelector;
import pl.shenlon.ga.selection.RankedSpecimenSelector;
import pl.shenlon.ga.selection.RouletteSpecimenSelector;
import pl.shenlon.poker.game.TestHand;
import pl.shenlon.poker.game.gametypes.PokerGame;
import pl.shenlon.poker.gui.testhands.TestHandFileFilter;

public class PopulationConfigPanel extends JPanel {

	public static final String WORK_ON_NEW_POPULATION = "Utwórz nową populację",
			WORK_ON_CURRENT_POPULATION = "Kontynuuj prace nad aktualną populacją";

	private JLabel mutationRateLabel, crossoverRateLabel, elitarismRateLabel,
			survivalRateLabel, populationSizeLabel, cashLabel,
			generationCountLabel, testHandLabel, testHandFilenameLabel,
			taskChoiceLabel;

	private SpinnerModel mutationRateModel, crossoverRateModel,
			elitarismRateModel, survivalRateModel, populationSizeModel,
			cashModel, generationCountModel;

	private JSpinner mutationRateSpinner, crossoverRateSpinner,
			elitarismRateSpinner, survivalRateSpinner, populationSizeSpinner,
			cashSpinner, generationCountSpinner;

	private Population population;

	private ButtonGroup specimenClassButtonGroup, evaluatorClassButtonGroup,
			gameTypeButtonGroup, selectorClassButtonGroup;

	private JLabel specimenClassLabel, evaluatorClassLabel, gameTypeLabel,
			selectorClassLabel;

	private JComboBox<String> taskChoiceComboBox;

	private JButton testHandSelectionFileButton;

	private File testHandsFile;

	private JFileChooser testHandsFileChooser;

	public PopulationConfigPanel() {
		initComponent();
	}

	private JPanel createJPanelFromButtonGroup(JLabel label,
			ButtonGroup buttonGroup) {
		JPanel panel = new JPanel(new MigLayout());
		panel.add(label, "wrap");
		Enumeration<AbstractButton> buttonGroupButtons = buttonGroup
				.getElements();
		while (buttonGroupButtons.hasMoreElements()) {
			panel.add(buttonGroupButtons.nextElement(), "wrap");
		}
		return panel;
	}

	private void disableSpecimenClasses() {
		String className = population.getSpecimenClass().getCanonicalName();
		Enumeration<AbstractButton> enumeration = specimenClassButtonGroup
				.getElements();
		while (enumeration.hasMoreElements()) {
			AbstractButton button = enumeration.nextElement();
			if (!button.getActionCommand().equals(className)) {
				button.setEnabled(false);
			}
		}
	}

	public int getGenerationsCount() {
		return (Integer) generationCountModel.getValue();
	}

	@SuppressWarnings("unchecked")
	public Population<?> getPopulation() {
		int size = (Integer) populationSizeModel.getValue();
		Population<?> population = null;
		switch (specimenClassButtonGroup.getSelection().getActionCommand()) {
			case "pl.shenlon.ga.SimpleSpecimen" :
				if (taskChoiceComboBox.getSelectedItem().equals(
						WORK_ON_NEW_POPULATION)) {
					population = new Population<SimpleSpecimen>(size,
							SimpleSpecimen.class);
				} else if (taskChoiceComboBox.getSelectedItem().equals(
						WORK_ON_CURRENT_POPULATION)) {
					population = new Population<SimpleSpecimen>(
							this.population,
							(Double) survivalRateModel.getValue(), size);
				}
				break;
			case "pl.shenlon.ga.StatefulSpecimen" :
				if (taskChoiceComboBox.getSelectedItem().equals(
						WORK_ON_NEW_POPULATION)) {
					population = new Population<StatefulSpecimen>(size,
							StatefulSpecimen.class);
				} else if (taskChoiceComboBox.getSelectedItem().equals(
						WORK_ON_CURRENT_POPULATION)) {
					population = new Population<StatefulSpecimen>(
							this.population,
							(Double) survivalRateModel.getValue(), size);
				}
				break;
		}
		population.setCash((Integer) cashModel.getValue());
		population.setMutationRate((Double) mutationRateModel.getValue());
		population.setCrossoverRate((Double) crossoverRateModel.getValue());
		population.setElitarismRate((Integer) elitarismRateModel.getValue());
		population.setSurvivalRate((Double) survivalRateModel.getValue());

		PopulationEvaluator populationEvaluator = null;
		switch (evaluatorClassButtonGroup.getSelection().getActionCommand()) {
			case "pl.shenlon.ga.evaluation.LinearPopulationEvaluator" :
				populationEvaluator = new LinearPopulationEvaluator(population);
				break;
			case "pl.shenlon.ga.evaluation.TournamentPopulationEvaluator" :
				populationEvaluator = new TournamentPopulationEvaluator(
						population);
				break;
		}
		try {
			Class<? extends PokerGame> pokerGameClass = (Class<? extends PokerGame>) Class
					.forName(gameTypeButtonGroup.getSelection()
							.getActionCommand());
			populationEvaluator.setPokerGameClass(pokerGameClass);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		population.setEvaluator(populationEvaluator);

		PopulationSpecimenSelector selector = null;
		switch (selectorClassButtonGroup.getSelection().getActionCommand()) {
			case "pl.shenlon.ga.selection.RankedSpecimenSelector" :
				selector = new RankedSpecimenSelector(population);
				break;
			case "pl.shenlon.ga.selection.RouletteSpecimenSelector" :
				selector = new RouletteSpecimenSelector(population);
				break;
		}
		population.setSelector(selector);
		// TODO this.population = population;
		return population;
	}

	private void initComponent() {
		setLayout(new MigLayout());

		populationSizeLabel = new JLabel("Rozmiar populacji");
		generationCountLabel = new JLabel("Liczba generacji");
		crossoverRateLabel = new JLabel("Współczynnik krzyżowania");
		mutationRateLabel = new JLabel("Współczynnik mutacji");
		survivalRateLabel = new JLabel("Współczynnik przeżywalności");
		elitarismRateLabel = new JLabel("Współczynnik elitaryzmu");
		cashLabel = new JLabel("Ilość gotówki");
		testHandLabel = new JLabel("Plik z rękami testowymi");
		testHandFilenameLabel = new JLabel("Nie wybrano");

		testHandSelectionFileButton = new JButton("Wybierz plik");
		testHandSelectionFileButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent event) {
				if (testHandsFileChooser == null) {
					testHandsFileChooser = new JFileChooser(new File(
							"data/testhands"));
					testHandsFileChooser
							.setFileFilter(new TestHandFileFilter());
					testHandsFileChooser.setAcceptAllFileFilterUsed(false);
				}
				int option = testHandsFileChooser
						.showOpenDialog(PopulationConfigPanel.this);
				if (option == JFileChooser.APPROVE_OPTION) {
					try {
						setTestHandFile(testHandsFileChooser.getSelectedFile());
					} catch (IOException ex) {
						JOptionPane
								.showMessageDialog(
										PopulationConfigPanel.this,
										"Wystąpił błąd w trakcie odczytywania pliku. Spróbuj ponownie później.",
										"Wystąpił błąd",
										JOptionPane.ERROR_MESSAGE);

					} catch (IllegalArgumentException ex) {
						JOptionPane
								.showMessageDialog(
										PopulationConfigPanel.this,
										"Plik zawiera niepoprawne dane. Lista rąk nie zostanie wczytana.",
										"Wystąpił błąd",
										JOptionPane.ERROR_MESSAGE);
					}
				}
			}
		});

		populationSizeModel = new SpinnerNumberModel(32, 32, 256, 2);
		populationSizeSpinner = new JSpinner(populationSizeModel);

		populationSizeModel.addChangeListener(new ChangeListener() {
			@Override
			public void stateChanged(ChangeEvent e) {
				int elitarismMaxValue = (Integer) populationSizeModel
						.getValue() / 10;
				SpinnerNumberModel elitarismModel = (SpinnerNumberModel) elitarismRateModel;
				if ((Integer) elitarismModel.getValue() > elitarismMaxValue) {
					elitarismModel.setValue(elitarismMaxValue);
				}
				elitarismModel.setMaximum(elitarismMaxValue);
			}
		});
		generationCountModel = new SpinnerNumberModel(10, 10, 10000, 100);
		generationCountSpinner = new JSpinner(generationCountModel);

		crossoverRateModel = new SpinnerNumberModel(0.7, 0.00, 1.0, 0.01);
		crossoverRateSpinner = new JSpinner(crossoverRateModel);

		mutationRateModel = new SpinnerNumberModel(0.3, 0.00, 1.0, 0.01);
		mutationRateSpinner = new JSpinner(mutationRateModel);

		survivalRateModel = new SpinnerNumberModel(0.8, 0.0, 1.0, 0.01);
		survivalRateSpinner = new JSpinner(survivalRateModel);

		elitarismRateModel = new SpinnerNumberModel(1, 1, 10, 1);
		elitarismRateSpinner = new JSpinner(elitarismRateModel);

		cashModel = new SpinnerNumberModel(1000, 100, 1000, 100);
		cashSpinner = new JSpinner(cashModel);

		add(populationSizeLabel, "w 300");
		add(generationCountLabel, "w 300, wrap, gap unrelated");
		add(populationSizeSpinner, "growx");
		add(generationCountSpinner, "growx, wrap, gap unrelated");

		add(crossoverRateLabel);
		add(mutationRateLabel, "wrap, gap unrelated");
		add(crossoverRateSpinner, "growx");
		add(mutationRateSpinner, "growx, wrap, gap unrelated");

		add(survivalRateLabel);
		add(elitarismRateLabel, "wrap, gap unrelated");
		add(survivalRateSpinner, "growx");
		add(elitarismRateSpinner, "growx, wrap, gap unrelated");

		add(cashLabel);
		add(testHandLabel, "wrap, gap unrelated");
		add(cashSpinner, "growx");
		add(testHandFilenameLabel, "gap unrelated, split 2");
		add(testHandSelectionFileButton, "growx, wrap, gap unrelated");

		evaluatorClassLabel = new JLabel("Typ ewaluatora:");
		// add(evaluatorClassLabel, "split");
		specimenClassLabel = new JLabel("Typ osobnika:");
		// add(specimenClassLabel, "gap unrelated");
		selectorClassLabel = new JLabel("Metoda selekcji:");
		// add(selectorClassLabel, "gap unrelated, wrap");
		gameTypeLabel = new JLabel("Typ gry");

		evaluatorClassButtonGroup = populateRadioButtonsFromAssets("evaluators");
		add(createJPanelFromButtonGroup(evaluatorClassLabel,
				evaluatorClassButtonGroup), "span, split 4, w 25%, top");

		specimenClassButtonGroup = populateRadioButtonsFromAssets("specimens");
		add(createJPanelFromButtonGroup(specimenClassLabel,
				specimenClassButtonGroup), "gap unrelated, w 25%, top");

		selectorClassButtonGroup = populateRadioButtonsFromAssets("selectors");
		add(createJPanelFromButtonGroup(selectorClassLabel,
				selectorClassButtonGroup), "top, gap unrelated, w 25%");

		gameTypeButtonGroup = populateRadioButtonsFromAssets("gametypes");
		add(createJPanelFromButtonGroup(gameTypeLabel, gameTypeButtonGroup),
				"w 25%, top, gap unrelated, wrap");

		taskChoiceLabel = new JLabel("Zadanie do wykonania");
		taskChoiceComboBox = new JComboBox<String>(
				new String[]{WORK_ON_NEW_POPULATION});
		taskChoiceComboBox.addItemListener(new ItemListener() {

			@Override
			public void itemStateChanged(ItemEvent event) {
				if (event.getStateChange() == ItemEvent.SELECTED) {
					switch (event.getItem().toString()) {
						case WORK_ON_NEW_POPULATION :
							Enumeration<AbstractButton> enumeration = specimenClassButtonGroup
									.getElements();
							while (enumeration.hasMoreElements()) {
								enumeration.nextElement().setEnabled(true);
							}
							break;
						case WORK_ON_CURRENT_POPULATION :
							disableSpecimenClasses();
							break;
					}
				}

			}
		});
		add(taskChoiceLabel, "span, wrap");
		add(taskChoiceComboBox, "growx, span, wrap");
	}

	private ButtonGroup populateRadioButtonsFromAssets(String assetsKey) {
		ButtonGroup buttonGroup = new ButtonGroup();
		AssetManager<?> evaluators = Assets.getAssetManager(assetsKey);
		for (String key : evaluators.keys()) {
			JRadioButton button = new JRadioButton(key);
			button.setSelected(true);
			button.setActionCommand(evaluators.getAsset(key).toString());
			buttonGroup.add(button);
		}

		return buttonGroup;
	}

	public void setPopulation(Population population) {
		this.population = population;
		if (taskChoiceComboBox.getModel().getSize() == 1) {
			taskChoiceComboBox.addItem(WORK_ON_CURRENT_POPULATION);
			taskChoiceComboBox.setSelectedItem(WORK_ON_CURRENT_POPULATION);
		}
		disableSpecimenClasses();

	}

	public void setTestHandFile(File file) throws IOException,
			IllegalArgumentException {
		Scanner in = null;
		try {
			in = new Scanner(file, "UTF-8");
			List<TestHand> testHands = new ArrayList<TestHand>();
			while (in.hasNextLine()) {
				testHands.add(new TestHand(in.nextLine()));
			}
			PopulationEvaluator.setTestHands(testHands);
			testHandsFile = file;
			testHandFilenameLabel.setText(testHandsFile.getName());
		} finally {
			in.close();
		}
	}
}